/*
 * Copyright 2008-2023 dexian.vip. All rights reserved.
 * Support: http://www.dexian.vip
 * License: http://www.dexian.vip/license
 */

package vip.dexian.core.base.dao;

import vip.dexian.common.query.Filter;
import vip.dexian.common.query.Listable;
import vip.dexian.common.query.Order;
import vip.dexian.common.query.Page;
import vip.dexian.common.query.Pageable;
import vip.dexian.common.utils.FilterUtils;
import vip.dexian.common.utils.NumberUtils;
import vip.dexian.core.base.bean.BaseEntity;
import vip.dexian.core.base.bean.IdEntity;
import vip.dexian.core.base.query.BaseQuery;
import vip.dexian.core.exception.EntityNotFoundException;
import jakarta.persistence.LockModeType;
import jakarta.persistence.TypedQuery;
import jakarta.persistence.criteria.CriteriaBuilder;
import jakarta.persistence.criteria.CriteriaQuery;
import jakarta.persistence.criteria.Predicate;
import jakarta.persistence.criteria.Root;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.util.Assert;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

/**
 * 数据库操作基础Dao 实现类
 *
 * @author 挺好的 2023年05月30日 15:40
 */
@Slf4j
public abstract class BaseDaoImpl<T extends BaseEntity <ID>, ID extends Serializable>
        extends BaseCriteriaQueryDaoImpl <T, ID> implements BaseDao <T, ID> {

    /**
     * 实体类不能为空消息
     */
    private static final String ENTITY_NOT_NULL_MESSAGE = "实体类不能为空";

    /**
     * 在初始化的时候，从泛型中得到具体的实体类类型
     */
    public BaseDaoImpl () {
        super();
    }

    /**
     * 根据ID查找实体类。该实体类处于托管状态。在同一个事务中，如果对该实体类进行属性修改，会发出update语句。
     * <p>
     * 在此接口在查询不到数据的时候，为什么不抛出异常（可能是自己封装的DaoException）
     *
     * @param id
     *         实体类ID
     *
     * @return 1. ID == null，返回null。2. 无法查找到数据，返回null。
     */
    @Override
    public T find (ID id) {
        return this.find(id, null);
    }

    /**
     * 根据ID查找实体类。当id为null或者实体类不存在，抛出异常
     *
     * @param id
     *         id
     * @param args
     *         国际化参数占位
     *
     * @return T
     *
     * @throws vip.dexian.core.exception.EntityNotFoundException
     *         id == null || 无法找到数据
     */
    @Override
    public T findByValidationIfNull (ID id, Object... args) {
        T entity = this.find(id);
        if (entity == null) {
            throw new EntityNotFoundException("00003", args);
        }

        return entity;
    }

    /**
     * 根据ID查找实体类，并且使用悲观锁
     *
     * @param id
     *         实体类ID
     *
     * @return 1. ID == null，返回null。2. 无法查找到数据，返回null。
     */
    @Override
    public T findByPessimisticWrite (ID id) {
        return this.find(id, LockModeType.PESSIMISTIC_WRITE);
    }

    /**
     * 根据ID查找实体类。该实体类处于托管状态。在同一个事务中，如果对该实体类进行属性修改，会发出update语句
     *
     * @param id
     *         实体类ID
     * @param lockModeType
     *         锁类型。主要有乐观锁和悲观锁，在读取数据的时候，请不要使用悲观锁读。而在写入一些重要数据，如：给操作余额、订单减库存等操作，应该使用悲观锁写入
     *         当该参数不传递的时候，同find(ID)方法一致
     *
     * @return 1. ID == null，返回null。2. 无法查找到数据，返回null。
     */
    @Override
    public T find (ID id, LockModeType lockModeType) {

        if (id == null) {
            return null;
        }

        return this.entityManager.find(this.entityClass, id, lockModeType);
    }

    /**
     * 查找单个对象
     *
     * @param filters
     *         查找条件
     *
     * @return 1. filters is empty，返回null。2. 无法查找到数据，返回null。
     */
    @Override
    public T find (List <Filter> filters) {
        if (CollectionUtils.isEmpty(filters)) {
            return null;
        }

        CriteriaQuery <T> criteriaQuery = this.createUniversalQuery();

        return super.find(criteriaQuery, filters);
    }

    /**
     * 查找单个对象
     *
     * @param filter
     *         查找条件
     *
     * @return @return 1. filter == null || filter is not valid，返回null。2. 无法查找到数据，返回null。
     */
    @Override
    public T find (Filter filter) {
        // 增加一个判断，避免创建List
        if (filter == null || !filter.isValid()) {
            return null;
        }
        List <Filter> filters = FilterUtils.builder().add(filter).build();
        return this.find(filters);
    }

    /**
     * 只查找1条数据。注意该方法默认的排序（如果未指定排序条件）。 如果继承自{@link BaseEntity}则按ID降序，
     * 继承自{@link vip.dexian.core.base.bean.OrderEntity} || {@link vip.dexian.core.base.bean.UniqueOrderEntity}则按order升序
     *
     * @param orders
     *         排序条件。
     *
     * @return 如果存在则返回，不存在返回null
     *
     * @see BaseDao#findList(Listable)
     */
    @Override
    public T get (List <Order> orders) {

        Listable listable = Listable.builder().count(1).orders(orders).build();

        List <T> entities = this.findList(listable);

        if (!CollectionUtils.isEmpty(entities)) {
            return entities.get(0);
        }
        return null;
    }

    /**
     * 查找列表
     *
     * @param listable
     *         列表参数
     *
     * @return 查找的实体类列表
     *
     * @see Listable
     */
    @Override
    public List <T> findList (Listable listable) {

        CriteriaQuery <T> criteriaQuery = this.createUniversalQuery();

        return this.findList(criteriaQuery, listable);
    }

    /**
     * 根据ID查询列表，该方法会校验会将重复的ID去除，使用IN查询。
     *
     * @param ids
     *         id列表
     *
     * @return 对应的实体列表
     */
    @Override
    public List <T> findList (List <ID> ids) {
        if (CollectionUtils.isEmpty(ids)) {
            return Collections.emptyList();
        }
        List <Filter> filters = FilterUtils.builder().inId(ids).build();

        Listable listable = Listable.builder().filters(filters).isEnableDefaultOrder(false).build();

        return this.findList(listable);
    }

    /**
     * 查找单个结果。注意该结果的排序方式， 如果继承自BaseEntity，默认按ID 降序， 继承自 OrderEntity，默认按排序升序
     *
     * @param listable
     *         搜索数据
     *
     * @return 查找的实体类
     */
    @Override
    public T find (Listable listable) {
        if (listable == null) {
            listable = new Listable();
            listable.setCount(1);
        }

        if (!NumberUtils.isPositive(listable.getCount())) {
            listable.setCount(1);
        }

        List <T> list = this.findList(listable);

        if (CollectionUtils.isEmpty(list)) {
            return null;
        }

        return list.get(0);
    }

    /**
     * 分页查找数据。
     *
     * @param pageable
     *         搜索条件，包含客户端的搜索信息以及分页信息
     *
     * @return 分页数据
     */
    @Override
    public Page <T> findPage (Pageable pageable) {

        CriteriaQuery <T> criteriaQuery = this.createUniversalQuery();

        return this.findPage(criteriaQuery, pageable);
    }

    /**
     * 分页查找数据，当{@link BaseDao#findPage(vip.dexian.common.query.Pageable)}无法满足查询需求时，可以通过该方法扩展。
     * <p>
     * 默认情况下，当前方法同{@link BaseDao#findPage(vip.dexian.common.query.Pageable)}功能一致，需要扩展，请在对应的Dao中重写该方法
     *
     * @param pageable
     *         搜索条件，包含客户端的搜索信息以及分页信息
     * @param query
     *         自定义查询条件
     * @param <Q>
     *         自定义查询条件
     *
     * @return 分页数据
     */
    @Override
    public <Q extends BaseQuery> Page <T> findPage (Pageable pageable, Q query) {
        return this.findPage(pageable);
    }

    /**
     * 统计
     *
     * @param filters
     *         搜索条件
     *
     * @return select count(*)的数量
     */
    @Override
    public long count (Filter... filters) {
        return this.count(new ArrayList <>(Arrays.asList(filters)));
    }

    /**
     * 统计
     *
     * @param filters
     *         搜索条件
     *
     * @return select count(*)的数量
     */
    @Override
    public long count (List <Filter> filters) {
        CriteriaQuery <T> criteriaQuery = this.createUniversalQuery();

        return this.count(criteriaQuery, filters);
    }


    /**
     * 查询某条记录是否存在，此时使用LIMIT 1的写法，避免使用SELECT COUNT
     *
     * @param filters
     *         查询条件
     *
     * @return 指定条件存在，返回true
     */
    @Override
    public boolean exists (List <Filter> filters) {

        CriteriaBuilder criteriaBuilder = this.entityManager.getCriteriaBuilder();

        CriteriaQuery <IdEntity> criteriaQuery = criteriaBuilder.createQuery(IdEntity.class);

        Root <T> root = criteriaQuery.from(this.entityClass);

        criteriaQuery.multiselect(root.get(BaseEntity.ID_PROPERTY_NAME));

        Predicate restrictions = criteriaBuilder.conjunction();

        // 其他查询条件
        Predicate predicate = this.toPredicate(root, filters);
        // 有其他的查询条件，则添加到当前条件中
        if (predicate != null) {
            restrictions = criteriaBuilder.and(restrictions, predicate);
        }

        criteriaQuery.where(restrictions);

        TypedQuery <IdEntity> query = this.entityManager.createQuery(criteriaQuery);

        query.setMaxResults(1);

        List <IdEntity> list = query.getResultList();

        return CollectionUtils.isNotEmpty(list);
    }

    /**
     * 获取实体类的ID
     *
     * @param entity
     *         实体类
     *
     * @return 返回实体类的ID
     */
    @Override
    public ID getIdentifier (T entity) {
        Assert.notNull(entity, ENTITY_NOT_NULL_MESSAGE);
        return (ID) this.entityManager.getEntityManagerFactory().getPersistenceUnitUtil().getIdentifier(entity);
    }

    /**
     * 实体类是否处于托管状态，如果处于托管状态，对于实体类的更新都会同步到数据库中
     *
     * @param entity
     *         实体类
     *
     * @return 是否是托管状态
     */
    @Override
    public boolean isManaged (T entity) {
        Assert.notNull(entity, ENTITY_NOT_NULL_MESSAGE);
        return this.entityManager.contains(entity);
    }

    /**
     * 实体类是否已加载
     *
     * @param entity
     *         实体类
     *
     * @return
     */
    @Override
    public boolean isLoaded (T entity) {
        Assert.notNull(entity, ENTITY_NOT_NULL_MESSAGE);

        return this.entityManager.getEntityManagerFactory().getPersistenceUnitUtil().isLoaded(entity);
    }

    /**
     * 实体类的某个属性是否已加载
     *
     * @param entity
     *         实体类
     * @param attributeName
     *         属性名称
     *
     * @return 是否已加载
     */
    @Override
    public boolean isLoaded (T entity, String attributeName) {
        Assert.notNull(entity, ENTITY_NOT_NULL_MESSAGE);
        Assert.hasText(attributeName, "属性名称不能为空");

        return this.entityManager.getEntityManagerFactory().getPersistenceUnitUtil().isLoaded(entity, attributeName);
    }

    /**
     * 保存实体类。持久化数据到数据库， 持久化完毕之后，实体类为托管状态。在同一个事务中，如果对该实体类进行属性修改，会发出update语句
     *
     * @param entity
     *         持久化的实体，不可为空
     */
    @Override
    public void persist (T entity) {
        Assert.notNull(entity, ENTITY_NOT_NULL_MESSAGE);
        // 在调用persist的时候，默认也会校验约束（Validator）
        this.entityManager.persist(entity);
    }

    /**
     * 1. 该方法与persist方法不同，persist没有返回值，该实例会添加到上下文中并对该实例进行管理，在同一个事务中，之后对于实例的任何属性进行修改，会发生update语句。
     * 2. merge方法返回实例副本（创建实体的新实例），该副本在上下文中并且受管理，在同一个事务中，
     * 之后对于实例副本的任何属性进行修改，会发生update语句。原实例则不被管理（所以对于原实例属性的任何修改，都不会发出update语句）。
     * <p>
     * 合并实体类，非托管状态的entity调用该方法，会执行插入操作。托管状态的则会进行更新操作
     *
     * <p>
     * ================瞬时状态=====================
     * 保存瞬时状态数据（没有id, 并且数据库中没有对应的数据），此时将持久化这个对象，并且返回这个持久化对象的引用。
     * ================瞬时状态=====================
     *
     * <p>
     * ================游离状态=====================
     * 保存游离状态数据，此时将持久化这个对象，并且返回这个持久化对象的引用，即便设置了id也没有用。
     * ================游离状态=====================
     *
     * <p>
     * ================托管状态=====================
     * 当实体类处于托管状态，调用该方法会执行更新操作
     * ================托管状态=====================
     *
     * @param entity
     *         实体类。
     *
     * @return 合并后的实例副本，该实例处于托管状态，在同一个事务中，任何对该实例副本进行的修改将会发出update语句
     */
    @Override
    public T merge (T entity) {
        Assert.notNull(entity, ENTITY_NOT_NULL_MESSAGE);
        return this.entityManager.merge(entity);
    }

    /**
     * 删除实体类
     *
     * @param entity
     *         实体类
     */
    @Override
    public void remove (T entity) {
        if (entity == null) {
            return;
        }
        this.entityManager.remove(entity);
    }

    /**
     * 刷新实体类，使对象的状态和数据表中对应的记录保持一致。强制执行一次select。 只有在托管状态的实体类才可以执行refresh。
     *
     * @param entity
     *         实体类.  entity == null || entity.isNew() 不进行任何操作
     */
    @Override
    public void refresh (T entity) {
        this.refresh(entity, null);
    }

    /**
     * 刷新实体类，使对象的状态和数据表中对应的记录保持一致。强制执行一次select。 只有在托管状态的实体类才可以执行refresh
     *
     * @param entity
     *         实体类。 entity == null || entity.isNew() 不进行任何操作
     * @param lockModeType
     *         锁类型。主要有乐观锁和悲观锁，在读取数据的时候，请不要使用悲观锁读。而在写入一些重要数据，如：给操作余额、订单减库存等操作，应该使用悲观锁写入
     *         当该参数不传递的时候，同refresh(T)方法一致
     */
    @Override
    public void refresh (T entity, LockModeType lockModeType) {

        if (entity == null || entity.isNew()) {
            return;
        }

        if (lockModeType != null) {
            this.entityManager.refresh(entity, lockModeType);
        } else {
            this.entityManager.refresh(entity);
        }
    }

    /**
     * 从上下文中删除给定的实体（变为游离状态/脱管状态），在调用之前如果有对实体进行未刷新的更改（包括删除）将不会同步到数据库。之前引用游离实体的将继续引用。
     *
     * @param entity
     *         要变为游离状态的实体类
     */
    @Override
    public void detach (T entity) {
        if (entity == null) {
            return;
        }

        this.entityManager.detach(entity);
    }

    /**
     * 获取当前实体的锁类型。 要调用该方法必须满足以下条件：
     * <p>
     * 1. 必须要在事务中调用该方法
     * 2. 实体类不能为空并且要有id
     * 3. 实体类处于托管状态
     *
     * @param entity
     *         实体。
     *
     * @return 返回锁类型。
     *
     * @see org.hibernate.LockMode
     */
    @Override
    public LockModeType getLockMode (T entity) {
        Assert.notNull(entity, ENTITY_NOT_NULL_MESSAGE);
        return this.entityManager.getLockMode(entity);
    }

    /**
     * 为指定实体加锁。调用该方法要满足以下条件：
     * <p>
     * 1、 实体类必须处于托管状态
     * 2、必须要在事务中调用该方法
     *
     * @param entity
     *         实体
     * @param lockModeType
     *         锁类型。主要有乐观锁和悲观锁，在读取数据的时候，请不要使用悲观锁读。而在写入一些重要数据，如：给操作余额、订单减库存等操作，应该使用悲观锁写入
     */
    @Override
    public void lock (T entity, LockModeType lockModeType) {
        if (entity == null || lockModeType == null) {
            return;
        }

        this.entityManager.lock(entity, lockModeType);
    }

    /**
     * 清空所有上下文中关联的实体。
     */
    @Override
    public void clear () {
        this.entityManager.clear();
    }

    /**
     * 刷新缓存。如果不调用flush方法， 什么时候执行插入或者更新无法确定。 当执行了flush以后， 会立即执行insert or update 语句。
     */
    @Override
    public void flush () {
        this.entityManager.flush();
    }
}
